Altinity's post Is It Safe Letting AI Agents Touch Production Data (Without the Tears) argues that prompts ("never drop tables") are suggestions, so the limits that keep an agent safe have to live in the infrastructure, where the agent can't override them. Below is every control it recommends, what QueryAPIGate does, and a one-line explanation of each.
QueryAPIGate covers about 80% of the checklist: essentially the whole data-and-identity layer. The remaining 20% is one separate layer: hardening the machine the agent runs on.
The article's recommendation on the left, what QueryAPIGate does on the right. Each row is explained below.
Where a point rests on a design decision, the ADR is the project's own record of it.
The agent only ever talks to QueryAPIGate, never to the database: it has no connection to override. Every request is checked by the gateway's grants and SQL guard, so a prompt that says "drop the table" has nothing to act on.
You mint scoped API keys (or roles that keys inherit) for agents instead of handing over an admin or personal account. Every key is read-only unless you explicitly grant writes, so read-only is the default rather than a reminder.
SELECT on specific tables onlyallowed_tables restricts a key to a named set of tables, enforced by parsing the SQL, not by trusting the query. A key also only reaches the connections it has been granted, so least privilege applies at both the connection and the table level.
Each key can carry a query timeout, a maximum rows per call and a maximum number of concurrent queries; these only narrow the server's own limits, never widen them. (Altinity's thread-per-query cap is a ClickHouse setting the database itself enforces.)
A key gets a rate limit (100/minute, say) plus optional usage quotas that count calls and rows per day or month, so a runaway agent can't bury the database in requests.
A JWT-bound user reaches only saved queries that take a parameter from their verified token claim (from_claim), so the "which rows" filter is signed into the request, not chosen by the agent. It is claim-bound parameterisation rather than arbitrary SQL row policies, and it covers the "see only your region" case cleanly.
Users sign in with a JWT from any OIDC provider (Keycloak, Okta, Entra), the MCP server accepts OAuth tokens through standard discovery, and the Console supports SSO. Identity comes from the identity provider, not a per-agent password.
JWTs expire on their own; API keys rotate (the old secret keeps working through a grace window) and can carry an expiry date; a token's claims map to a role and workspace, so you grant and revoke centrally instead of editing a password list.
This is precisely what QueryAPIGate is (ADR 0006): it sits between the agent and the database and exposes only governed saved queries and a read-only ad-hoc tool.
Each client presents its own key or bearer token, which authorizes the gateway only. The database credentials live inside the gateway, encrypted at rest, and are never sent to the LLM, so the model can't reuse them directly.
The ad-hoc MCP SQL tool is read-only no matter what the key is otherwise allowed to do: a deliberately strict default for agent traffic.
Agents probe the schema to find data, so describe_table and schema browsing are masking-aware: a masked or dropped column is flagged, never shown as readable. An agent can't even learn that a hidden column holds real values.
Every grant, mask, limit and row filter resolves from the authenticated key or user, so the same query returns different results, or an access denied, depending on who is asking.
Every action is recorded in a hash-chained audit log: each entry is hashed over the previous one, so changing, removing or reordering a record is detectable (ADR 0012). Runs are kept as history, and the log can stream to a SIEM. The article under-emphasises this; it is a QueryAPIGate strength.
When an agent asks to write over MCP, the request is held for a person to approve and then runs exactly once, with exactly the arguments shown (ADR 0008): the human checkpoint the article calls the safest pattern.
QueryAPIGate is the OAuth resource server (RFC 9728), so callback-URL registration lives at your identity provider, where the spec places it. The token's audience (aud) is bound to the MCP server's own URL, so a token issued for REST, or anything else, is refused, and it can't be replayed against the database.
Out of scope. QueryAPIGate governs the data path; it doesn't control the container or machine the agent runs on.
Out of scope, and complementary: the gateway removes one sharp object (direct database access), but hardening the agent's process and host is a separate job.
Partial. QueryAPIGate builds the approval checkpoint for data writes, but an agent authoring and running infrastructure tooling (Terraform, bash) is broader than what a data-access gateway does.
Altinity's examples target ClickHouse; QueryAPIGate applies the same controls across MySQL, PostgreSQL, ClickHouse, DuckDB, MongoDB, H2, JDBC and Trino. Coverage was assessed against the QueryAPIGate codebase at v0.40.0; ADR references are the project's own.
The MCP server itself is on the MCP page; the standards behind these controls on Standards and specifications; the reasoning in the threat model. To try it: guide 24.